/*
 Xern Entity Relocation Network
 Copyright (C) 2011 Ronie Salgado <roniesalg@gmail.com>

 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.
*/

#include "Xern/Json.hpp"

namespace Xern
{
	inline bool IsWhite(int c)
	{
		return c <= ' ';
	}

	inline bool IsAlpha(int c)
	{
		return (c >= 'a' && c <= 'z') ||
			   (c >= 'A' && c <= 'Z');
	}

	inline bool IsDigit(int c)
	{
		return c >= '0' && c <= '9';
	}

	static void SkipWhite(std::istream &in)
	{
		while(IsWhite(in.peek()))
			in.get();
	}

	// JsonString
	std::ostream &operator<<(std::ostream &out, const JsonString &s)
	{
		// Begin string.
		out << '"' ;

		// Write string data.
		JsonString::const_iterator it = s.begin();
		for(; it != s.end(); it++)
		{
			JsonString::value_type c = *it;
			switch(c)
			{
			case '"':
				out << "\\\"";
				break;
			case '\\':
				out << "\\\\";
				break;
			case '\b':
				out << "\\b";
				break;
			case '\f':
				out << "\\f";
				break;
			case '\n':
				out << "\\n";
				break;
			case '\r':
				out << "\\r";
				break;
			case '\t':
				out << "\\t";
				break;
			default:
				// Write the character verbatim.
				out << c;
				break;
			}
		}

		// End string
		out << '"';
		return out;
	}

	std::istream &operator>>(std::istream &in, JsonString &s)
	{
		// Clear the string.
		s.data.clear();

		// Skip white spaces.
		SkipWhite(in);

		// Read the string start.
		int c = in.get();
		if(c != '"')
			throw JsonException("Expected string");

		// Parse the string content.
		for(;;)
		{
			if(in.eof())
				throw JsonException("unexpected eof.");

			// Read the next char
			c = in.get();

			// Check for special chars.
			if(c == '\\')
			{
				// Got a escape sequence, read the type.
				c = in.get();
				switch(c)
				{
				case '"':
					s.data.push_back('"');
					break;
				case '\\':
					s.data.push_back('\\');
					break;
				case 'b':
					s.data.push_back('\b');
					break;
				case 'f':
					s.data.push_back('\f');
					break;
				case 'n':
					s.data.push_back('\n');
					break;
				case 'r':
					s.data.push_back('\r');
					break;
				case 't':
					s.data.push_back('\t');
					break;
				case 'u':
				{
					// Read unicode character.
					char code[5];
					for(int i = 0; i < 4; i++)
					{
						code[i] = in.get();
						if(in.eof())
							throw JsonException("unexpected eof.");
					}
					code[4] = 0;

					// Parse the character hexadecimal.
					int uc = strtol(code, NULL, 16);

					// Write the utf-8 encoded character.
					if(uc <= 0x007F)
					{
						// Ascii character.
						s.data.push_back(uc);
					}
					else if(uc <= 0x07FF)
					{
						s.data.push_back(0xc0 | ((uc & 0x07C0) >> 6));
						s.data.push_back(0x80 |  (uc & 0x003F));
					}
					else if(uc <= 0xFFFF)
					{
						s.data.push_back(0xe0 | ((uc & 0xF000) >> 12));
						s.data.push_back(0x80 | ((uc & 0x0FC0) >> 6));
						s.data.push_back(0x80 |  (uc & 0x003F));
					}
					else
					{
						// Shouldn't reach here.
						assert(NULL);
					}
				}
					break;
				default:
					s.data.push_back(c);
					break;
				}
			}
			else if(c == '"')
			{
				// End of string.
				break;
			}
			else
			{
				// Store the char.
				s.data.push_back(c);
			}
		}

		return in;
	}

	// JsonObject
	std::ostream &operator<<(std::ostream &out, const JsonObject &o)
	{
		out << "{";
		JsonObject::const_iterator it = o.begin();
		bool prev = false;
		for(; it != o.end(); it++)
		{
			if(prev)
				out << ",";
			else
				prev = true;

			out << it->first << ":" << it->second;
		}

		out << "}";

		return out;
	}

	std::istream &operator>>(std::istream &in, JsonObject &o)
	{
		// Clear the object.
		o.data.clear();

		// Skip whites.
		SkipWhite(in);

		// Match the '{'
		if(in.get() != '{')
			throw JsonException("Expected '{'.");

		SkipWhite(in);

		while(in.peek() != '}')
		{
			JsonString key;
			JsonValue value;

			// Read the key.
			in >> key;

			// Skip white.
			SkipWhite(in);

			if(in.get() != ':')
				throw JsonException("Expected ':'.");

			// Read the value.
			in >> value;

			// Store the property.
			o.data.insert(std::make_pair(key, value));

			// Skip white.
			SkipWhite(in);

			if(in.peek() == ',')
			{
				in.get();
			}
			else if(in.peek() == '}')
			{
				break;
			}
			else
			{
				throw JsonException("Unexpected character.");
			}

		}

		// Match the '}'
		if(in.get() != '}')
			throw JsonException("Expected '}'.");

		return in;
	}

	// JsonArray
	std::ostream &operator<<(std::ostream &out, const JsonArray &a)
	{
		out << "[";
		JsonArray::const_iterator it = a.begin();
		bool prev = false;
		for(; it != a.end(); it++)
		{
			if(prev)
				out << ",";
			else
				prev = true;

			out << *it;
		}
		out << "]";

		return out;
	}

	std::istream &operator>>(std::istream &in, JsonArray &a)
	{
		// Clear the array.
		a.data.clear();

		// Skip white spaces.
		SkipWhite(in);

		// Match a '['
		if(in.get() != '[')
			throw JsonException("Expected '['.");

		SkipWhite(in);

		// Read the values.
		while(in.peek() != ']')
		{
			JsonValue value;
			in >> value;
			a.push_back(value);

			// Skip white.
			SkipWhite(in);

			// Check for ','
			if(in.peek() == ',')
			{
				in.get();
			}
			else if(in.peek() == ']')
			{
				break;
			}
			else
				throw JsonException("unexpected character");
		}

		// Match a ']'
		if(in.get() != ']')
			throw JsonException("Expected ']'.");

		return in;
	}

	// JsonNull
	std::ostream &operator<<(std::ostream &out, const JsonNull &a)
	{
		out << "null";
		return out;
	}

	std::istream &operator>>(std::istream &in, JsonNull &a)
	{
		std::string ident;
		SkipWhite(in);
		while(IsAlpha(in.peek()))
			ident.push_back(in.get());

		if(!IsWhite(in.peek()))
			throw JsonException("Unexpected character.");

		if(ident != "null")
			throw JsonException("Unexpected identifier.");

		return in;
	}

	// JsonValue
	std::istream &operator>>(std::istream &in, JsonValue &value)
	{
		// Skip white.
		SkipWhite(in);

		int c = in.peek();
		if(IsAlpha(c))
		{
			// Read identifier.
			std::string ident;
			while(IsAlpha(in.peek()))
			{
				ident.push_back(in.get());
			}

			// Check for only alphanumeric.
			if(IsDigit(in.peek()))
				throw JsonException("Unexpected character.");

			// Parse the null or bool
			if(ident == "null")
			{
				value = JsonNull();
			}
			else if(ident == "false")
			{
				value = false;
			}
			else if(ident == "true")
			{
				value = true;
			}
		}
		else if(IsDigit(c) || c == '-' || c == '.')
		{
			std::string number;
			bool integer = true;

			if(c == '-')
			{
				number.push_back('-');
				c = in.get();
			}

			// Read the integer part.
			while(IsDigit(in.peek()))
				number.push_back(in.get());

			c = in.peek();
			if(c == '.')
			{
				integer = false;

				// Read the fractional part
				number.push_back('.');
				in.get();
				while(IsDigit(in.peek()))
					number.push_back(in.get());

				c = in.peek();
			}

			if(c == 'e' || c == 'E')
			{
				integer = false;
				// Read the exponent
				number.push_back('e');
				in.get();

				// Read the exponent sign.
				c = in.peek();
				if(c == '+' || c == '-')
				{
					number.push_back(c);
					in.get();
				}

				// Read the exponent.
				while(IsDigit(in.peek()))
					number.push_back(in.get());
			}

			// Process the number.
			if(integer)
				value = atoi(number.c_str());
			else
				value = atof(number.c_str());
		}
		else if(c == '{')
		{
			// Parse the object.
			value = JsonObject();
			in >> boost::get<JsonObject> (value);
		}
		else if(c == '[')
		{
			// Parse the array.
			value = JsonArray();
			in >> boost::get<JsonArray> (value);
		}
		else if(c == '"')
		{
			// Parse the string.
			value = JsonString();
			in >> boost::get<JsonString> (value);
		}
		else
		{
			throw JsonException("Unexpected character.");
		}

		return in;
	}
}
